using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.Web;
using System.Web.SessionState;
using System.Reflection;
using Newtonsoft.Json;
using System.IO;
using JsonHTTPHandler.Errors;
using JsonHTTPHandler.Formatter;

namespace JsonHTTPHandler
{
  /// <summary>
  /// Grabs requests and sends them off to the JSON handler
  /// </summary>
  /// <remarks>To capture requests, use the URL format http://site.com/.json/(Class Name)/(Method Name)</remarks>
  public class JSONHandler : IHttpHandler, IRequiresSessionState
  {
    private static Dictionary<string, Type> services;

    #region IHttpHandler Members
    public bool IsReusable
    {
      get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
      CallInput callInput = null;
      string result = string.Empty;
      try
      {
        string url = context.Request.RawUrl;
        if (url.Contains("json"))
        {
          callInput = new CallInput(context);
          result = ExecuteMethod(callInput);
        }
      }
      catch (JSONHandlerException jsEx)
      {
        result = JavaScriptConvert.SerializeObject(jsEx.Error);
      }
      catch (Exception ex)
      {
        JSONError error = new JSONError();
        error.Message = ex.Message;
        error.Detail = ex.ToString();
        error.RootMessage = ex.GetBaseException().ToString();

        result = JavaScriptConvert.SerializeObject(error);

        if (callInput != null &&
          callInput.ShowJsonResult)
        {
          result = JsonFormatter.WriteJSONObjectToHTML(result);
        }
      }
      context.Response.Write(result);
    }
    #endregion

    #region Method Execution
    private string ExecuteMethod(CallInput callInput)
    {
      string jsonText = null;

      // Find the method
      MethodInfo method = FindMethod(callInput);

      // Create an instance of the type
      object declaringType = Activator.CreateInstance(method.DeclaringType);

      // Execute the method
      object result = method.DeclaringType.InvokeMember(method.Name,
        BindingFlags.InvokeMethod,
        null,
        declaringType,
        callInput.Arguments);

      jsonText = JavaScriptConvert.SerializeObject(result);

      if (callInput.ShowJsonResult)
      {
        jsonText = JsonFormatter.WriteJSONObjectToHTML(jsonText);
      }

      return jsonText;
    }

    private MethodInfo FindMethod(CallInput callInput)
    {
      MethodInfo method = null;

      if(services.ContainsKey(callInput.ServiceName))
      {
        Type type = services[callInput.ServiceName];
        object[] attributes = type.GetCustomAttributes(typeof(JsonServiceAttribute), true);
        if (type.Name == callInput.ServiceName &&
          attributes.Length > 0)
        {
          JsonServiceAttribute serviceAttribute = (JsonServiceAttribute)attributes[0];
          method = MatchMethodParameters(type, callInput, serviceAttribute);
        }
        else
        {
          throw new JSONHandlerException(new ServiceNotFoundError(callInput.ServiceName));
        }
      }
      else
      {
        throw new JSONHandlerException(new ServiceNotFoundError(callInput.ServiceName));
      }

      if (method == null)
      {
        throw new JSONHandlerException(new MethodNotFoundError(callInput.MethodName));
      }
      return method;
    }

    private MethodInfo MatchMethodParameters(Type type, 
      CallInput callInput,
      JsonServiceAttribute serviceAttribute)
    {
      // Get all methods in this type
      MethodInfo[] methods = type.GetMethods();
      MethodInfo foundMethod = null;
      foreach (MethodInfo method in methods)
      {
        // Does the method have the JsonMethodAttribute
        object[] attributes = method.GetCustomAttributes(typeof(JsonMethodAttribute), false);
        if(attributes.Length == 0)
        {
          continue;
        }
        
        // Update the CallInput object to ignore underscore params
        callInput.RemoveUnderscoreParameters();

        JsonMethodAttribute methodAttribute = (JsonMethodAttribute)attributes[0];

        if (method.Name == callInput.MethodName &&
          method.GetParameters().Length == callInput.Parameters.Count)
        {
          // Check the parameters
          ParameterInfo[] parameters = method.GetParameters();
          bool gotAllParameters = true;
          // Check the length
          if (parameters.Length == callInput.Parameters.Count)
          {
            foreach (ParameterInfo parameter in parameters)
            {
              if (!callInput.Parameters.ContainsKey(parameter.Name))
              {
                gotAllParameters = false;
                break;
              }
              else
              {
                callInput.Parameters[parameter.Name].ParameterDetails = parameter;
              }
            }
          }

          if (gotAllParameters)
          {
            foundMethod = method;
            break;
          }
        }
      }
      return foundMethod;
    }
    #endregion

    #region Service Registration
    public static void RegisterAllServices()
    {      
      // Load all services
      foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
      {
        try
        {
          if (assembly.ManifestModule.ScopeName == "Default Dynamic Module")
          {
            continue;
          }
          RegisterAssembly(assembly);
        }
        catch (Exception ex)
        {
          string message = ex.ToString();
        }
      }
    }

    public static void RegisterAssembly(Assembly assembly)
    {
      foreach (Type type in assembly.GetExportedTypes())
      {
        if (type.FullName.StartsWith("Microsoft") ||
          type.FullName.StartsWith("System"))
        {
          continue;
        }

        RegisterService(type);
      }
    }

    public static void RegisterService(Type type)
    {
      if (services == null)
      {
        services = new Dictionary<string, Type>();
      }

      if (type.GetCustomAttributes(typeof(JsonServiceAttribute), false).Length > 0)
      {
        services.Add(type.Name, type);
      }
      else
      {
        throw new Exception(String.Format("Type {0} does not have a JsonServiceAttribute.", type.FullName));
      }
    }

    public static void DeregisterService(Type type)
    {
      if (services == null)
      {
        services = new Dictionary<string, Type>();
      }
      services.Remove(type.Name);
    }
    #endregion
  }
}
